/*
 * Created on 03.apr.2006
 * Created by Tomas Ekeli
 */
package placing;

import java.io.Serializable;
import java.util.LinkedList;
import java.util.List;
import java.util.NoSuchElementException;

import people.Person;
import people.PersonNotFoundException;
import people.Reserver;
import util.Debug;

/**
 * 
 * A Place represents an available place in the real world. It retains 
 * information about the actual place in the world and whether or not it is
 * currently occupied. A place can be occupied by none or one Person. 
 * A Place can be reserved for a Person or a Group. The Place also stores a 
 * history of previous occupants. 
 * 
 * @author Tomas Ekeli
 *
 */
public class Place implements Comparable<Place>, Serializable{
	private static final long serialVersionUID = 1L;
	
	public static final String ELEMENT_NAME = "PLACE";
	private int id;
	private Room room;
	private boolean isFree;
	private LinkedList<Person> history;
	private LinkedList<Reservation> reservationsList;
	private Person prefByPerson;
	private boolean isReserved;

	public Place() {
		id = -1;
	}
	
	/**
	 * The standard constructor for a place with the given id
	 * @param r The room this place is in
	 * @param pl The id of the place, should be unique in the room
	 */
	public Place(Room r, int pl) {
		room = r;
		id = pl;
		history = new LinkedList<Person>();
		isFree= true;
		isReserved = false;
	}

	/**
	 * Compares the Place to another Place, based on the identifying number of
	 * the Place. If the Places are not in the same room, the comparison is based
	 * on the id of the Rooms they are in.
	 */
	public int compareTo(Place o) {
		if (getRoomID() != o.getRoomID()) {
			return getRoomID() - o.getRoomID();
		}
		return id - o.getPlaceID();
	}
	
	@Override
	public boolean equals(Object o) {
		return (compareTo((Place)o) == 0);
	}

	public String toString() {
		if (Debug.ON) {
			String sReturnMe = "";
			if (!isFree) {
				return getPlaceName() + " assigned to " + history.getLast().getName();
			}
			sReturnMe = getPlaceName() + " (free space";
			if (!history.isEmpty()) {
				sReturnMe += ", formerly" +
						" " + history.getLast().getName();
			}
			sReturnMe += ")";
			
			if (isReserved()) {
				sReturnMe += " R:(";
				for (Reservation r : reservationsList) {
					sReturnMe += r;
				}
				sReturnMe += ")";
			}
			
			return sReturnMe;
		} else {
			return getPlaceName();
		}
	}

	/**
	 * @return The Place's identifying number. This number is internal to
	 * the Room the Place is in.
	 */
	public int getPlaceID() {
		return id;
	}
	
	/**
	 * @return The identifying number of the Roome the Place is in.
	 */
	protected int getRoomID() {
		return room.getID();
	}
	
	/**
	 * @param r The Room this Place should live in.
	 */
	protected void setRoom(Room r) {
		room = r;
	}
	
	/**
	 * @return The full name of the place in the format "roomNumber-placeNumber"
	 * (ie. place 23 in room 150 would return the name "150-23").
	 */
	public String getPlaceName() {
		return room.getID() + "-" + id;
	}
	
	/**
	 * Assigns the Person to this Place. If the Place is reserved, and the 
	 * Person is not included in the Reservation (s)he is not added and an
	 * exception is raised.
	 * 
	 * @param current The Person to assign as the current owner of this Place
	 * @throws ReservationConflictException The Person did not qualify for this
	 * reserved Place.
	 */
	protected void assign(Person current) throws ReservationConflictException {
		//is there a reservation?
		if (isReserved) {
			//is the Person valid?
			if (!checkReservations(current)) {
				throw new ReservationConflictException("Place " +  getPlaceName() 
						+ " is not available to " + current.getName());
			}
		}
		
		//add the Person to this place, and mark as occupied		
		history.add(current);
		isFree = false;
		current.assign(this);
		if (Debug.ON) {
			try {
				System.out.println(getPlaceName() + " now contains "
						+ getCurrent().getName());
			} catch (PersonNotFoundException e) {
				System.out.println("Strange goings on in place assign(Person)");
			}
		}		
	}
	
	/**
	 * @return The most recent (and presumably current) student on this Place.
	 * If there was never a student at this place <code>null</code> is returned.
	 */
	protected Person getCurrent() throws PersonNotFoundException{
		if (isFree) {
			throw new PersonNotFoundException("Place: " +  id + " no current occupant found.");
		}
		try {
			return history.getLast();
		}
		catch (NoSuchElementException e) { //shouldn't happen, but we allow for it
//			bFree = true; //was nobody there - it's free.
			throw new PersonNotFoundException("Place: " +  id + " no current occupant found, despite not marked as free.");
		}
	}
	
	public List<Person> getHistory() {
		return new LinkedList<Person>(history);
	}

	/**
	 * Clears the history of this Place. Marks it as free.
	 *
	 */
	protected void clearHistory() {
		history.clear();
		isFree = true;
	}

	/**
	 * Checks whether the Person is valid for the Place, and whether the Place
	 * is available.
	 * 
	 * @param p The Person to check
	 * @return true iff the Person can have this place
	 */
	protected boolean isValidAndFree(Person p) {
		if (!isFree) {
			if (Debug.ON) {
				System.out.println(id + " is taken");
			}
			return false;
		}
		if (!isReserved()) {
			if (Debug.ON) {
				System.out.println(id + " is free and unreserved.");
			}
			return true;
		}
		if (Debug.ON) {
			System.out.print(id + " is free and reserved ");
			if (checkReservations(p)){
				System.out.println("and VALID.");
			}
			else {
				System.out.println("and INVALID");
			}
		}
		return checkReservations(p);
	}
	
	/**
	 * Checks whether the Person is valid for the Place. If the Place is taken
	 * by p it will still check for validity.
	 * 
	 * @param p The Person to check for
	 * @return true iff p is valid for the Place, and it is free; or assigned to
	 * p and valid for p
	 */
	protected boolean isValid(Person p) {
		if (!isFree) {
			try { //Should be able to validate on its current Place
				if (p == getCurrent()) {
					isFree = true;
					boolean result = isValidAndFree(p);
					isFree = false;
					return result;
				}
				else {
					return false;
				}
			} catch (PersonNotFoundException e) {
				// impossible
				e.printStackTrace();
				return false;
			}
		}
		else {
			return isValidAndFree(p);
		}
	}

	/**
	 * @return true iff the Place is free (as in unoccupied)
	 */
	public boolean isFree() {
		return isFree;
	}
	
	/**
	 * A reservation on a room will restrict acces to the room to only the
	 * Persons that are included in the reservation.
	 * @return true iff the Place has a reservation of some sort
	 */
	public boolean isReserved() {
		return isReserved;
	}
	
	public List<Reservation> getReservations() throws ReservationConflictException {
		if (isReserved()) {
			return new LinkedList<Reservation>(reservationsList);
		} else {
			throw new ReservationConflictException("No reservation found");
		}
	}
	
	/**
	 * Adds r to the group of reservers allowed on the Place
	 * @param r The reserver with access to the Place
	 */
	protected void reserve(Reserver r) {
		if (reservationsList == null) {
			reservationsList = new LinkedList<Reservation>();
			isReserved = true;
		}
		if (Debug.ON) {
			System.out.println("Reserving " + getPlaceName());
		}
		Reservation res = new Reservation();
		res.reserve(r);
		reservationsList.add(res);
	}
	
	
	/**
	 * Returns the number of discrete reservations this Place has. This can
	 * give a rough indication of how many People may get this Place, but 
	 * every Group and every singly-reserved Person counts as one reservation. 
	 * 
	 * @return The number of reservations on the Place
	 */
	protected int getNumReservations() {
		if (reservationsList == null) {
			return 0;
		}
		return reservationsList.size();
	}

	/**
	 * Removes all reservations on the Place, in effect opening it to all Persons-
	 *
	 */
	protected void clearReservation() {
		reservationsList = null;
		isReserved = false;
	}

	/**
	 * The Place is the preffered Place of a Person. If the Place already has a
	 * preferred Person, the Person with the highest score will win.
	 *  
	 * @param person The Person who prefers this Place
	 */
	public void setPreferredBy(Person person) {
		if (prefByPerson == null) {
			prefByPerson = person;
		}
		else {
			int comp = person.compareTo(prefByPerson);
			if (comp < 0) {
				prefByPerson = person;
			}
		}
	}
	
	/**
	 * Checks whether the Place is preferred by some Person
	 * @return <code>true</code> iff the Place is preferred by some Person
	 */
	public boolean isPreferredBy() {
		return !(prefByPerson == null);
	}
	
	/**
	 * Returns the currently preferred Person of the Place
	 * @return The Person with preference.
	 */
	public Person getPreferredBy() {
		return prefByPerson;
	}

	private boolean checkReservations(Person current) {
		for (Reservation r : reservationsList) {
			if (r.validate(current)) {
				if (Debug.ON) {
					System.out.println(current.getName() + " validates on "
							+ getPlaceName());
				}
				return true;
			}
		}
		
		return false;
	}

}
